"""
此文件定义方言实现
"""
import re
import logging
from functools import wraps
from typing import Union
from sqlalchemy import sql, types, util, exc
from sqlalchemy.engine import default, reflection
from sqlalchemy.sql import compiler, Select, expression
import sqlalchemy_tdengine.types as tdtypes


_logger = logging.getLogger('sqlalchemy-tdengine.dialect')

try:
    import taos
    from taos import TaosConnection
    from taos import error
except ModuleNotFoundError as e:
    _logger.error(f'导入taos失败: 请安装taospy(pip install taospy=>2.2.1)')
except OSError as e:
    _logger.error('导入taos动态库失败, 参考: https://www.taosdata.com/docs/cn/v2.0/connector#%E8%BF%9E%E6%8E%A5%E5%99%A8')
from . import types as sqltyps

# db保留字
RESERVED_WORDS = {
    'ABORT', 'CREATE', 'IGNORE', 'NULL', 'STAR', 'ACCOUNT', 'CTIME', 'IMMEDIATE', 'OF', 'STATE', 'ACCOUNTS',
    'DATABASE', 'IMPORT', 'OFFSET', 'STATEMENT', 'ADD', 'DATABASES', 'IN', 'OR', 'STATE_WINDOW', 'AFTER', 'DAYS',
    'INITIALLY', 'ORDER', 'STORAGE', 'ALL', 'DBS', 'INSERT', 'PARTITIONS', 'STREAM', 'ALTER', 'DEFERRED', 'INSTEAD',
    'PASS', 'STREAMS', 'AND', 'DELIMITERS', 'INT', 'PLUS', 'STRING', 'AS', 'DESC', 'INTEGER', 'PPS', 'SYNCDB', 'ASC',
    'DESCRIBE', 'INTERVAL', 'PRECISION', 'TABLE', 'ATTACH', 'DETACH', 'INTO', 'PREV', 'TABLES', 'BEFORE', 'DISTINCT',
    'IS', 'PRIVILEGE', 'TAG', 'BEGIN', 'DIVIDE', 'ISNULL', 'QTIME', 'TAGS', 'BETWEEN', 'DNODE', 'JOIN', 'QUERIES',
    'TBNAME', 'BIGINT', 'DNODES', 'KEEP', 'QUERY', 'TIMES', 'BINARY', 'DOT', 'KEY', 'QUORUM', 'TIMESTAMP', 'BITAND',
    'DOUBLE', 'KILL', 'RAISE', 'TINYINT', 'BITNOT', 'DROP', 'LE', 'REM', 'TOPIC', 'BITOR', 'EACH', 'LIKE', 'REPLACE',
    'TOPICS', 'BLOCKS', 'END', 'LIMIT', 'REPLICA', 'TRIGGER', 'BOOL', 'EQ', 'LINEAR', 'RESET', 'TSERIES', 'BY',
    'EXISTS', 'LOCAL', 'RESTRICT', 'UMINUS', 'CACHE', 'EXPLAIN', 'LP', 'ROW', 'UNION', 'CACHELAST', 'FAIL', 'LSHIFT',
    'RP', 'UNSIGNED', 'CASCADE', 'FILE', 'LT', 'RSHIFT', 'UPDATE', 'CHANGE', 'FILL', 'MATCH', 'SCORES', 'UPLUS',
    'CLUSTER', 'FLOAT', 'MAXROWS', 'SELECT', 'USE', 'COLON', 'FOR', 'MINROWS', 'SEMI', 'USER', 'COLUMN', 'FROM',
    'MINUS', 'SESSION', 'USERS', 'COMMA', 'FSYNC', 'MNODES', 'SET', 'USING', 'COMP', 'GE', 'MODIFY', 'SHOW', 'VALUES',
    'COMPACT', 'GLOB', 'MODULES', 'SLASH', 'VARIABLE', 'CONCAT', 'GRANTS', 'NCHAR', 'SLIDING', 'VARIABLES', 'CONFLICT',
    'GROUP', 'NE', 'SLIMIT', 'VGROUPS', 'CONNECTION', 'GT', 'NONE', 'SMALLINT', 'VIEW', 'CONNECTIONS', 'HAVING', 'NOT',
    'SOFFSET', 'VNODES', 'CONNS', 'ID', 'NOTNULL', 'STABLE', 'WAL', 'COPY', 'IF', 'NOW', 'STABLES', 'WHERE'
}


def debug(func):
    @wraps(func)
    def wap(*args, **kwargs):
        res = func(*args, **kwargs)
        print(f'函数{func.__name__}, 返回值为: {res}')
        return res
    return wap



class TDENGINEIdentifierPreparer(compiler.IdentifierPreparer):
    reserved_words = RESERVED_WORDS


class TDENGINEDDLCompiler(compiler.DDLCompiler):
    pass




class TDENGINECompiler(compiler.SQLCompiler):

    def __init__(self, dialect, statement, schema_translate_map='dbtable', **kwargs):
        super().__init__(dialect, statement, **kwargs)

    @debug
    def visit_alias(self, *args, **kwargs):
        return super().visit_alias(*args, **kwargs)

    @debug
    def visit_between_op_binary(self, *args, **kwargs):
        return super().visit_between_op_binary(*args, **kwargs)

    @debug
    def visit_binary(self, *args, **kwargs):
        return super().visit_binary(*args, **kwargs)

    @debug
    def visit_bindparam(self, *args, **kwargs):
        return super().visit_bindparam(*args, **kwargs)

    @debug
    def visit_case(self, *args, **kwargs):
        return super().visit_case(*args, **kwargs)

    @debug
    def visit_cast(self, *args, **kwargs):
        return super().visit_cast(*args, **kwargs)

    @debug
    def visit_clauselist(self, *args, **kwargs):
        return super().visit_clauselist(*args, **kwargs)


    @debug
    def visit_collation(self, *args, **kwargs):
        return super().visit_collation(*args, **kwargs)

    @debug
    def visit_column(self, *args, **kwargs):
        return super().visit_column(*args, **kwargs)

    @debug
    def visit_compound_select(self, *args, **kwargs):
        return super().visit_compound_select(*args, **kwargs)

    @debug
    def visit_contains_op_binary(self, *args, **kwargs):
        return super().visit_contains_op_binary(*args, **kwargs)

    @debug
    def visit_cte(self, *args, **kwargs):
        return super().visit_cte(*args, **kwargs)

    @debug
    def visit_custom_op_binary(self, *args, **kwargs):
        return super().visit_custom_op_binary(*args, **kwargs)

    @debug
    def visit_custom_op_unary_modifier(self, *args, **kwargs):
        return super().visit_custom_op_unary_modifier(*args, **kwargs)

    @debug
    def visit_custom_op_unary_operator(self, *args, **kwargs):
        return super().visit_custom_op_unary_operator(*args, **kwargs)

    @debug
    def visit_delete(self, *args, **kwargs):
        return super().visit_delete(*args, **kwargs)

    @debug
    def visit_empty_set_expr(self, *args, **kwargs):
        return super().visit_empty_set_expr(*args, **kwargs)

    @debug
    def visit_empty_set_op_expr(self, *args, **kwargs):
        return super().visit_empty_set_op_expr(*args, **kwargs)

    @debug
    def visit_endswith_op_binary(self, *args, **kwargs):
        return super().visit_endswith_op_binary(*args, **kwargs)

    @debug
    def visit_extract(self, *args, **kwargs):
        return super().visit_extract(*args, **kwargs)

    @debug
    def visit_false(self, *args, **kwargs):
        return super().visit_false(*args, **kwargs)

    @debug
    def visit_fromclause(self, *args, **kwargs):
        return super().visit_fromclause(*args, **kwargs)

    @debug
    def visit_funcfilter(self, *args, **kwargs):
        return super().visit_funcfilter(*args, **kwargs)

    @debug
    def visit_function(self, *args, **kwargs):
        return super().visit_function(*args, **kwargs)

    @debug
    def visit_function_as_comparison_op_binary(self, *args, **kwargs):
        return super().visit_function_as_comparison_op_binary(*args, **kwargs)

    @debug
    def visit_grouping(self, *args, **kwargs):
        return super().visit_grouping(*args, **kwargs)

    @debug
    def visit_ilike_op_binary(self, *args, **kwargs):
        return super().visit_ilike_op_binary(*args, **kwargs)

    @debug
    def visit_index(self, *args, **kwargs):
        return super().visit_index(*args, **kwargs)

    @debug
    def visit_insert(self, *args, **kwargs):
        return super().visit_insert(*args, **kwargs)

    @debug
    def visit_is_false_unary_operator(self, *args, **kwargs):
        return super().visit_is_false_unary_operator(*args, **kwargs)

    @debug
    def visit_is_true_unary_operator(self, *args, **kwargs):
        return super().visit_is_true_unary_operator(*args, **kwargs)

    @debug
    def visit_join(self, *args, **kwargs):
        return super().visit_join(*args, **kwargs)

    @debug
    def visit_label(self, *args, **kwargs):
        return super().visit_label(*args, **kwargs)

    @debug
    def visit_label_reference(self, *args, **kwargs):
        return super().visit_label_reference(*args, **kwargs)

    @debug
    def visit_lambda_element(self, *args, **kwargs):
        return super().visit_lambda_element(*args, **kwargs)

    @debug
    def visit_lateral(self, *args, **kwargs):
        return super().visit_lateral(*args, **kwargs)

    @debug
    def visit_like_op_binary(self, *args, **kwargs):
        return super().visit_like_op_binary(*args, **kwargs)

    @debug
    def visit_mod_binary(self, *args, **kwargs):
        return super().visit_mod_binary(*args, **kwargs)

    @debug
    def visit_next_value_func(self, *args, **kwargs):
        return super().visit_next_value_func(*args, **kwargs)

    @debug
    def visit_not_between_op_binary(self, *args, **kwargs):
        return super().visit_not_between_op_binary(*args, **kwargs)

    @debug
    def visit_not_contains_op_binary(self, *args, **kwargs):
        return super().visit_not_contains_op_binary(*args, **kwargs)

    @debug
    def visit_not_endswith_op_binary(self, *args, **kwargs):
        return super().visit_not_endswith_op_binary(*args, **kwargs)

    @debug
    def visit_not_ilike_op_binary(self, *args, **kwargs):
        return super().visit_not_ilike_op_binary(*args, **kwargs)

    @debug
    def visit_not_in_op_binary(self, *args, **kwargs):
        return super().visit_not_in_op_binary(*args, **kwargs)

    @debug
    def visit_not_like_op_binary(self, *args, **kwargs):
        return super().visit_not_like_op_binary(*args, **kwargs)

    @debug
    def visit_not_match_op_binary(self, *args, **kwargs):
        return super().visit_not_match_op_binary(*args, **kwargs)

    @debug
    def visit_not_regexp_match_op_binary(self, *args, **kwargs):
        return super().visit_not_regexp_match_op_binary(*args, **kwargs)

    @debug
    def visit_not_startswith_op_binary(self, *args, **kwargs):
        return super().visit_not_startswith_op_binary(*args, **kwargs)

    @debug
    def visit_null(self, *args, **kwargs):
        return super().visit_null(*args, **kwargs)

    @debug
    def visit_over(self, *args, **kwargs):
        return super().visit_over(*args, **kwargs)

    @debug
    def visit_regexp_match_op_binary(self, *args, **kwargs):
        return super().visit_regexp_match_op_binary(*args, **kwargs)

    @debug
    def visit_regexp_replace_op_binary(self, *args, **kwargs):
        return super().visit_regexp_replace_op_binary(*args, **kwargs)

    @debug
    def visit_release_savepoint(self, *args, **kwargs):
        return super().visit_release_savepoint(*args, **kwargs)

    @debug
    def visit_rollback_to_savepoint(self, *args, **kwargs):
        return super().visit_rollback_to_savepoint(*args, **kwargs)

    @debug
    def visit_savepoint(self, *args, **kwargs):
        return super().visit_savepoint(*args, **kwargs)

    @debug
    def visit_scalar_function_column(self, *args, **kwargs):
        return super().visit_scalar_function_column(*args, **kwargs)

    @debug
    def visit_select(self, *args, **kwargs):
        return super().visit_select(*args, **kwargs)

    @debug
    def visit_select_statement_grouping(self, *args, **kwargs):
        return super().visit_select_statement_grouping(*args, **kwargs)


    @debug
    def visit_sequence(self, *args, **kwargs):
        return super().visit_sequence(*args, **kwargs)

    @debug
    def visit_startswith_op_binary(self, *args, **kwargs):
        return super().visit_startswith_op_binary(*args, **kwargs)

    @debug
    def visit_subquery(self, *args, **kwargs):
        return super().visit_subquery(*args, **kwargs)

    @debug
    def visit_table(self, *args, **kwargs):
        return super().visit_table(*args, **kwargs)

    @debug
    def visit_table_valued_alias(self, *args, **kwargs):
        return super().visit_table_valued_alias(*args, **kwargs)

    @debug
    def visit_table_valued_column(self, *args, **kwargs):
        return super().visit_table_valued_column(*args, **kwargs)

    @debug
    def visit_tablesample(self, *args, **kwargs):
        return super().visit_tablesample(*args, **kwargs)

    @debug
    def visit_textclause(self, *args, **kwargs):
        # TODO 1
        res = super().visit_textclause(*args, **kwargs)
        if res and len(res) > 1:
            res = res.split('.')[0] + '\''
        return res

    @debug
    def visit_textual_label_reference(self, *args, **kwargs):
        return super().visit_textual_label_reference(*args, **kwargs)

    @debug
    def visit_textual_select(self, *args, **kwargs):
        return super().visit_textual_select(*args, **kwargs)

    @debug
    def visit_true(self, *args, **kwargs):
        return super().visit_true(*args, **kwargs)

    @debug
    def visit_tuple(self, *args, **kwargs):
        return super().visit_tuple(*args, **kwargs)

    @debug
    def visit_type_coerce(self, *args, **kwargs):
        return super().visit_type_coerce(*args, **kwargs)

    @debug
    def visit_typeclause(self, *args, **kwargs):
        return super().visit_typeclause(*args, **kwargs)

    @debug
    def visit_unary(self, *args, **kwargs):
        return super().visit_unary(*args, **kwargs)

    @debug
    def visit_unsupported_compilation(self, *args, **kwargs):
        return super().visit_unsupported_compilation(*args, **kwargs)

    @debug
    def visit_update(self, *args, **kwargs):
        return super().visit_update(*args, **kwargs)

    @debug
    def visit_values(self, *args, **kwargs):
        return super().visit_values(*args, **kwargs)

    @debug
    def visit_withingroup(self, *args, **kwargs):
        return super().visit_withingroup(*args, **kwargs)

    @debug
    def group_by_clause(self, select, **kw):
        group_str = super().group_by_clause(select, **kw)
        group_str = group_str.replace('GROUP BY ', '').replace(' ', '')
        column_list = group_str.split(',')
        result_list = []
        for column in column_list:
            res = re.search('.+?\((?P<key>.+?)\)', column)
            if res:
                result_list.append(res.group('key'))
            else:
                result_list.append(column)
        return f' GROUP BY {", ".join(result_list)}'


    @debug
    def limit_clause(self, select, **kw):
        return super().limit_clause(select, **kw)

    @debug
    def order_by_clause(self, select, **kw):
        """
        排序格式化字符串
        涛思仅允许对timestamp列进行排序
        """
        order_by_str = super().order_by_clause(select, **kw)
        return order_by_str

    @debug
    def get_select_precolumns(self, select, **kw):
        return super().get_select_precolumns(select, **kw)


# 方言实现
class TDENGINEDialect(default.DefaultDialect):
    name = "tdengine"
    default_paramstyle = 'format'

    statement_compiler = TDENGINECompiler
    # type_compiler = None
    ddl_compiler = TDENGINEDDLCompiler

    preparer = TDENGINEIdentifierPreparer
    # execution_ctx_cls = None
    # inspector = None

    # convert_unicode = False
    # supports_unicode_statements = True
    # supports_unicode_binds = True
    # requires_name_normalize = True
    #
    # supports_sequences = True
    # supports_native_decimal = True

    # supports_comments = True

    ischema_names = {}
    colspecs = {
        types.Boolean: sqltyps.BOOL,
        types.DateTime: sqltyps.TIMESTAMP,
        types.Integer: sqltyps.INT,
        types.String: sqltyps.NCHAR,
        types.JSON: sqltyps.JSON,
        types.Float: sqltyps.FLOAT,
    }

    # postfetch_lastrowid = False
    # implicit_returning = False
    # supports_empty_insert = False
    # supports_native_boolean = True
    # supports_default_values = False
    # supports_sane_multi_rowcount = False
    # isolation_level = None

    max_identifier_length = 127

    def connect(self, *cargs, **cparams) -> TaosConnection:
        """
        实现数据库连接
        :param cargs:
        :param cparams:
        :return: connect
        """
        cparams['user'] = cparams.get('username')
        cparams.pop('username')
        return self.dbapi.connect(**cparams)

    @classmethod
    def dbapi(cls) -> taos:
        """
        定义外部依赖
        :return: taos
        """
        taos.paramstyle = cls.default_paramstyle
        return taos

    # 获取模式列表, stable, table
    def get_schema_names(self, connection, **kwargs):
        return list([
            'stable', 'table'
        ])

    # 获取stable, table列表
    def get_table_names(self, connection, schema=None, **kwargs):
        schema = (schema or 'table') + 's'

        result = connection.execute(sql.text(f'show {schema}'))
        tables = list([
            self.normalize_name(row[0]) for row in result.fetchall()
        ])
        return tables

    # 获取列配置
    def get_columns(self, connection, table_name, schema=None, **kw):
        _logger.info('进入获取列')
        result = connection.execute(sql.text(f'desc {table_name}'))
        columns = []
        for row in result.fetchall():
            name = row[0]
            type = row[1]
            length = row[2]
            note = row[3]
            obj = {
                'name': name,
                'default': None,
                'comment': None,
                'nullable': True,
            }
            print('循环', name, type, length, note)
            if type == 'TIMESTAMP':
                obj['type'] = tdtypes.TIMESTAMP()
            elif type == 'TINYINT':
                obj['type'] = tdtypes.TINYINT()
            elif type == 'SMALLINT':
                obj['type'] = tdtypes.SMALLINT()
            elif type == 'INT':
                obj['type'] = tdtypes.INT()
            elif type == 'BIGINT':
                obj['type'] = tdtypes.BIGINT()
            elif type == 'FLOAT':
                obj['type'] = tdtypes.FLOAT()
            elif type == 'DOUBLE':
                obj['type'] = tdtypes.DOUBLE()
            elif type == 'BOOL':
                obj['type'] = tdtypes.BOOL()
            elif type == 'BINARY':
                obj['type'] = tdtypes.BINARY(length=length)
            elif type == 'NCHAR':
                obj['type'] = tdtypes.NCHAR(length=length)
            elif type == 'JSON':
                obj['type'] = tdtypes.JSON()
            if note:
                obj['tag'] = True
            columns.append(obj)
        print('循环结束', columns)
        _logger.info(f'columns: {columns}')
        return columns

    # 获取主键
    def get_primary_keys(self, connection, table_name, schema=None, **kw):
        result = connection.execute(sql.text(f'desc {table_name}'))
        for row in result.fetchall():
            name = row[0]
            return name

    # 获取外键
    def get_foreign_keys(self, *args, **kwargs):
        return []

    def get_indexes(self, *args, **kwargs):
        return []

    def has_table(self, connection, table_name, schema=None, **kw):
        try:
            connection.execute(sql.text(f'desc {table_name}'))
            return True
        except Exception as e:
            return False

    def _check_unicode_returns(self, connection, additional_tests=None):
        return False